package com.mini.core.generator;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.sql.Connection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;

import com.mini.core.utils.CommonJdbc;
import com.mini.core.utils.CommonUtil;
import com.mini.core.utils.MiniUtil;
import com.mini.core.utils.StrKit;

/**
 * 实体类代码生成工具
 * 
 * 可以生成单表、生成所有表（可以做过滤处理、类名影射）、根据文件配置生成指定的表等
 * 在多库的情况，建议每个库一个package，避免重名
 * 
 * @author fengli
 * @date 2014-7-7 下午5:03:56
 * 
 */
public class EntityGenerator {

	private static FileOutputStream fos = null;
	private static OutputStreamWriter writer = null;
	public static final String ENCODING_GBK = "GBK";
	public static final String ENCODING_UTF8 = "UTF-8";
	
	public static void main(String[] args) {
		try {
			generateTable(CommonJdbc.MYSQL,"127.0.0.1",3306,"springmvc","root","11111","t_depart",null,"Depart","com.mini.example.bean","D:\\",ENCODING_UTF8);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 获取类名，去除package
	 * @param clazz
	 * @return
	 */
	public static String getShortClassName(Class<?> clazz) {
		String cn = clazz.getName();
		if (cn != null && cn.contains(".")) {
			return cn.substring(cn.lastIndexOf(".") + 1);
		}
		return "";
	}
	
	/**
	 * 获取Dialect
	 * @param dialect
	 * @return
	 */
	private static Map<String, Class<?>> getDialectMapping(String dialect){
		if(CommonJdbc.MYSQL.equalsIgnoreCase(dialect))
			return new MysqlTypeMapping().getMapping();
		if(CommonJdbc.ORACLE.equalsIgnoreCase(dialect))
			return new OracleTypeMapping().getMapping();
		if(CommonJdbc.SQLSERVER.equalsIgnoreCase(dialect))
			return new SqlServerTypeMapping().getMapping();
		else
			return null;
	}

	/**
	 * 生成单个表结构对应的Bean，属性名和字段名保持一致
	 * @param dialect           数据库类型
	 * @param ip 				数据库IP
	 * @param port				数据库端口
	 * @param database			数据库名
	 * @param userName			数据库用户名
	 * @param password			数据库密码
	 * @param table				表名
	 * @param annotationTable 	注解表名，默认和表名一直，但是分表的时候需要填写为前缀
	 * @param className			生成类名，为null或空则默认使用表名，首字母大写，
	 * 							如果类名包含完整的package路径，则忽略packageName参数
	 * @param packageName		包名
	 * @param targetDir			目标生成文件目录
	 * @param encoding			文件编码
	 * @throws Exception
	 */
	public static void generateTable(String dialect,String ip, int port, String database,
			String userName, String password, String table, String annotationTable, String className,
			String packageName, String targetDir, String encoding)
			throws Exception {
		Map<String, Class<?>> mapping = getDialectMapping(dialect);
		if (!targetDir.endsWith("/")) {
			targetDir += "/";
		}
		
		if (className == null || className.trim().length() == 0) {
			className = StrKit.firstCharToUpperCase(table);
		}
		
		boolean isMultipleTable = true;
		if (annotationTable == null || annotationTable.trim().length() == 0) {
			annotationTable = table;
			isMultipleTable = false;
		}
		
		String shrotClassName = className;
		
		File dir = null;
		if (className.contains(".")) {
			shrotClassName = className.substring(className.lastIndexOf(".") + 1);
			dir = new File(targetDir + className.substring(0, className.lastIndexOf(".")).replace(".", "/"));
		} else {
			dir = new File(targetDir + packageName.replace(".", "/"));
		}
		
		if (!dir.exists()) {
			dir.mkdirs();
		}

		fos = new FileOutputStream(
				new File(dir.getPath() + "/" + shrotClassName + ".java"));
		writer = new OutputStreamWriter(fos, encoding);

		Connection conn = CommonJdbc.getConnection(dialect,ip, port, database,userName, password);
		List<String> primaryKeys = CommonJdbc.getPrimaryKeys(conn,table);
		List<Map<String, String>> list = CommonJdbc.getColumns(conn, database,table);
		conn.close();

		Set<String> imports = new HashSet<String>();
		imports.add("com.mini.core.BaseEntity");
		imports.add("java.io.Serializable");
		imports.add("com.mini.core.annotation.Entity");
		
		if (list != null && list.size() > 0) {
			for (Map<String, String> m : list) {
				String type = MiniUtil.caps(m.get("t")).trim();
				Class<?> clazz = mapping.get(type.toUpperCase());
				if (!clazz.getName().startsWith("java.lang.")) {
					imports.add(clazz.getName());
				}
			}
		}

		// 生成package、import 信息
		writeLine("package " + packageName + ";");
		writeLine("");
		for (String i : imports) {
			writeLine("import " + i + ";");
		}
		writeLine("");

		// 生成注释信息
		writeLine("/**");
		if (isMultipleTable) {
			writeLine(" * Entity for table '" + database + "." + annotationTable + "*' generated by sxjun");
		} else {
			writeLine(" * Entity for table '" + database + "." + annotationTable + "' generated by sxjun");
		}
		writeLine(" * ");
		writeLine(" * @author sxjun");
		writeLine(" * @date " + CommonUtil.getCurrentDateTime());
		writeLine(" */");
		
		//获取主键
		String ids = "";
		for(String primaryKey : primaryKeys){
			ids += primaryKey+",";
		}
		ids = ids.substring(0, ids.length()-1);

		// 注解信息
		writeLine("@Entity(table=\"" + annotationTable + "\",id=\""+ids+"\")");

		// 类名信息
		writeLine("public class " + className
				+ " extends BaseEntity implements Serializable {\n");
		Random r = new Random();
		long l = r.nextLong();
		writeLine("\tpublic static final long serialVersionUID = " + l + "L;");
		writeLine("");

		writeLine("\t/**");
		writeLine("\t * 表字段定义静态类");
		writeLine("\t */");
		// 列静态类
		writeLine("\tpublic static final class Columns {");
		for (Map<String, String> m : list) {
			writeLine("\t\tpublic static final String " + m.get("c") + " = \""
					+ m.get("c") + "\";");
		}
		writeLine("\t}");
		writeLine("");
		
		writeLine("");
		// getter setter信息
		for (Map<String, String> m : list) {
			String type = MiniUtil.caps(m.get("t")).trim();
			String c = m.get("c");
			String cn = getShortClassName(mapping.get(type.toUpperCase()));
			writeLine("\tpublic "+className+" set" + StrKit.firstCharToUpperCase(c)
					+ " (" + cn + " " + c + ")" + " {");
			writeLine("\t\tsuper.set(Columns." + c + ", " + c + ");");
			writeLine("\t\treturn this;");
			writeLine("\t}");

			writeLine("\tpublic " + cn + " get"
					+ StrKit.firstCharToUpperCase(c) + " ()" + " {");
			writeLine("\t\treturn super.get(\"" + c + "\");");
			writeLine("\t}");
		}
		
		writeLine("}");
		writer.flush();
		writer.close();
	}

	/**
	 * 生成所有表结构对应的Bean，默认采用表名作类名，首字母大写，如果某些表需要指定特定格式类名的，则初始化classNameMapping进行设置
	 * 
	 * @param ip 					数据库IP
	 * @param port					数据库端口
	 * @param database				数据库名
	 * @param userName				数据库用户名
	 * @param password				数据库密码
	 * @param includeTables			包含的表名（通常是针对要过滤的表名设置，保存一个接口）
	 * @param excludeTables			要过滤的表名（分表，只需要生成一个，包含字符串的模式）
	 * @param classNameMapping		类名映射，如果不采用默认类名，需要指定表-类名映射
	 * @param packageName			包名
	 * @param targetDir				生成目标路劲
	 * @param encoding				文件编码
	 * @throws Exception
	 */
	public static void generateAllTable(String dialect, String ip, int port, String database,
			String userName, String password, Map<String, String> includeTables,
			String[] excludeTables, Map<String, String> classNameMapping,
			String packageName, String targetDir, String encoding)
			throws Exception {
		Connection conn = CommonJdbc.getConnection(dialect,ip, port, database,
				userName, password);
		Set<String> tables = CommonJdbc.getAllTables(conn, (String[])includeTables.keySet().toArray(), excludeTables);
		conn.close();
		if (tables != null && tables.size() > 0) {
			for (String table : tables) {
				String className = classNameMapping.get(table);
				if (className == null) {
					className = StrKit.firstCharToUpperCase(table);
				}
				generateTable(dialect,ip, port, database, userName, password, table, includeTables.get(table),
						className, packageName,
						targetDir, encoding);
			}
		}
	}
	
	/**
	 * 
	 * （推荐这种模式）根据配置文件统一生成Entity，对于存在多个数据库的情况还有点麻烦，需要多个配置文件并多次调用。
	 * 
	 * 文档格式，1列或者2列或者3列，逗号分割，
	 * 1列：表名，实体类注解表名采用实际表名，类名采用实际表名（首字母大写）
	 * 2列（通常是因为表名不规则）：第一列为表名，第二列为类名（package可写可不写，不写采用默认），这种方式，实体类注解表名采用实际表名
	 * 3列（通常是因为分表）：第一列为表名，第二列为实体类注解表名，第三列才类名（package可写可不写，不写采用默认）
	 * 
	 * demo:
	 * 表名,类名
	 * table					默认类名（规则是第一个字母大写，这个生成的类名为Table）
	 * table_test,TableTest		指定类名（非分表，即注解的table名为table_test,生成类名为TableTest）
	 * table_00,table_,Table	分表并指定类名（第一列为随机取一个分表名，第二列为注解table前缀为table_，第三列为该系列分表的统一类名Table）
	 * ...
	 * 
	 * @param ip				数据库IP
	 * @param port				数据库端口
	 * @param database			数据库名
	 * @param userName			数据库用户名
	 * @param password			数据库密码
	 * @param configFilePath	配置文件路径
	 * @param packageName		包名
	 * @param targetDir			生产目标文件路径
	 * @param encoding			编码
	 * @throws Exception
	 */
	public static void generateByFile(String dialect,String ip, int port, String database, String userName, String password, 
			String configFilePath, String packageName, String targetDir, String encoding)
			throws Exception {
		FileReader fr = new FileReader(configFilePath);
		BufferedReader br = new BufferedReader(fr);
	    String line = br.readLine();
	    while (line != null) {
	    	if (line.trim().length() > 0) {
	    		if (line.contains(",")) {
	    			String[] buf = line.split(",");
	    			// 自定义类名
	    			if (buf.length == 2) {// 非分表
	    				generateTable(dialect,ip, port, database, userName, password, 
	    					buf[0].trim(), "", buf[1].trim(), packageName, targetDir, encoding);
	    			} else if (buf.length == 3) {// 分表
	    				generateTable(dialect,ip, port, database, userName, password, 
		    					buf[0].trim(), buf[1].trim(), buf[2].trim(), packageName, targetDir, encoding);
	    			}
	    			
	    		} else {
	    			// 默认类名
	    			generateTable(dialect,ip, port, database, userName, password, 
	    					line.trim(), "", StrKit.firstCharToUpperCase(line.trim()), 
	    					packageName, targetDir, encoding);
	    		}
	    	}
	    	line = br.readLine();
	    }
	    fr.close();
	    br.close();
	}

	/**
	 * 写一行代码
	 * @param line
	 * @return
	 */
	private static void writeLine(String line) throws IOException {
		System.out.println(line);
		writer.write(line + "\r\n");
	}
}
